using System;
using Gtk;

public partial class MainWindow : Gtk.Window
{
	public class MechanicState {
		public double t;
		public double x1, y1, x2, y2, vx1, vy1, vx2, vy2, m1, m2;
		public double G = 1;
		public double dt = 0.01;
		
		public MechanicState ()
		{
			// Initial condition
			t = 0;
			x1 = 0; y1 = 0;
			vx1 = 0; vy1 = .5;
			m1 = 1; 
	
			x2 = 1; y2 = 0;
			vx2 = 0; vy2 = -.5;
			m2 = 1;
		}

		public double TotalEnergy ()
		{
			return 0.5 * m1 * (vx1*vx1 + vy1*vy1)
				+ 0.5 * m2 * (vx2*vx2 + vy2*vy2)
				- G * m1 * m2 / Math.Sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) );
		}
		
		public static MechanicState ToNext(MechanicState t)
		{
			MechanicState k1 = t.TimeDeriv ();
			MechanicState k2 = t.Add (k1, t.dt/2).TimeDeriv ();
			MechanicState k3 = t.Add (k2, t.dt/2).TimeDeriv ();
			MechanicState k4 = t.Add (k3, t.dt).  TimeDeriv ();
			MechanicState next = t.Add (k1.Add(k2).Add(k3).Add(k4), t.dt/6);
			next.t = t.t + t.dt;
			return next;
		}
		
		public MechanicState TimeDeriv ()
		{
			MechanicState d = new MechanicState ();
			d.x1 = vx1;
			d.y1 = vy1;
			d.x2 = vx2;
			d.y2 = vy2;
			
			double r = Math.Sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) );
			double r3 = r*r*r;
			d.vx1 = - G * m2 / r3 * (x1-x2);
			d.vy1 = - G * m2 / r3 * (y1-y2);
			d.vx2 = - G * m2 / r3 * (x2-x1);
			d.vy2 = - G * m2 / r3 * (y2-y1);
			
			return d;
		}
		
		public MechanicState Add(MechanicState d, double dt) 
		{
			MechanicState s = new MechanicState ();
			s = this;
			s.t += dt;
			s.x1 += d.x1 * dt;
			s.y1 += d.y1 * dt;
			s.x2 += d.x2 * dt;
			s.y2 += d.y2 * dt;
			
			s.vx1 += d.vx1 * dt;
			s.vy1 += d.vy1 * dt;
			s.vx2 += d.vx2 * dt;
			s.vy2 += d.vy2 * dt;
			return s;
		}

		public MechanicState Add(MechanicState d)
		{
			return this.Add(d, 1);
		}
		
	}

	double m_timeStep = 0;
	MechanicState m_state = new MechanicState ();
	uint m_timer = 0;

	public MainWindow () : base(Gtk.WindowType.Toplevel)
	{
		Build ();
		
	}

	protected void OnDeleteEvent (object sender, DeleteEventArgs a)
	{
		Application.Quit ();
		a.RetVal = true;
	}

	protected virtual void OnQuit (object sender, System.EventArgs e)
	{
		Application.Quit ();
	}
	
	protected virtual void OnPlay (object sender, System.EventArgs e)
	{
		if (m_timer == 0) {
			txtLog.Buffer.Text = "Start playing\n";
			m_timer = GLib.Timeout.Add (50, new GLib.TimeoutHandler(PlayNext));
			btnPlay.Label = "Pause";
		}
		else {
			btnPlay.Label = "Play";
			GLib.Source.Remove(m_timer);
			m_timer = 0;
		}
	}
	
	bool PlayNext ()
	{
		txtLog.Buffer.Text += string.Format ("step {0}, e = {1} \n", m_timeStep,
		                                     m_state.TotalEnergy ());
		ScrollToEnd (txtLog);
		
		int width, height;
		double x_unit = 40, y_unit = 40;
		Gdk.Window win = drwArea.GdkWindow;
		Gdk.GC gc = new Gdk.GC (win);

		win.Clear();
		win.GetSize(out width, out height);
		
		gc.Fill = Gdk.Fill.Solid;
		Gdk.Color red_color = new Gdk.Color (0xff, 0, 0);
		Gdk.Color blue_color = new Gdk.Color (0, 0, 0xff);
		Gdk.Colormap colormap = Gdk.Colormap.System;
		colormap.AllocColor(ref red_color, true, true);
		colormap.AllocColor(ref blue_color, true, true);

		gc.Foreground = red_color;
		win.DrawArc(gc, true, width / 2 + (int)(m_state.x1 * x_unit), height / 2 - (int) (m_state.y1 * y_unit), 5, 5, 0, 360*64); // full circle

		gc.Foreground = blue_color;
		win.DrawArc(gc, true, width / 2 + (int)(m_state.x2 * x_unit), height / 2 - (int) (m_state.y2 * y_unit), 5, 5, 0, 360*64); // full circle

		m_state = MechanicState.ToNext (m_state);
		return true;
	}
	
	void ScrollToEnd (Gtk.TextView txtLog)
	{
		TextIter ti = txtLog.Buffer.GetIterAtLine(txtLog.Buffer.LineCount-1); 
		TextMark tm = txtLog.Buffer.CreateMark("eot", ti,false); 
		txtLog.ScrollToMark(tm, 0, false, 0, 0); 
	}
	
	protected virtual void OnReset (object sender, System.EventArgs e)
	{
		m_state = new MechanicState ();
		m_timeStep = 0;		
	}
	
	
}

